package com.ly.mp.busicen.rule.instrumentation.plugin;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import com.ly.mp.busicen.rule.flow.FlowSpelUtil;
import com.ly.mp.busicen.rule.flow.IDataVolume;
import com.ly.mp.busicen.rule.flow.IFlowContext;
import com.ly.mp.busicen.rule.flow.IFlowResult;
import com.ly.mp.busicen.rule.flow.IFlowResultCtn;
import com.ly.mp.busicen.rule.flow.IFlowVolume;
import com.ly.mp.busicen.rule.flow.action.IAction;
import com.ly.mp.busicen.rule.flow.action.IActionResult;
import com.ly.mp.busicen.rule.flow.action.IActionResult.Signal;
import com.ly.mp.busicen.rule.flow.action.OperationType;
import com.ly.mp.busicen.rule.flow.filter.FlowFilter;
import com.ly.mp.busicen.rule.flow.filter.FlowInvocation;
import com.ly.mp.busicen.rule.flow.filter.FlowResult;
import com.ly.mp.busicen.rule.instrumentation.FlowInstruUtils;
import com.ly.mp.busicen.rule.instrumentation.IFlowInstrumentation;

/**
 * <FONT style="FONT-SIZE:12pt; ">来吧，调试</FONT>
 * 
 * @author ly-liuweisong
 *
 */
@Component
public class DebugInstrumentation implements IFlowInstrumentation {

	public DebugInstrumentation() {

	}

	@Value("${xrule.debug.type:null}")
	private String debugType;

	Map<String, DebugConf> DbConfig = new ConcurrentHashMap<>();

	@Override
	public void beginFlow(String flow, String brand, Map<String, Object> data, String rid) {
		anayliseDebugType(rid);
	}

	@Override
	public void endBuildContext(IFlowContext context) {
		String rid = FlowInstruUtils.getXruleRid(context);
		if (DbConfig.get(rid) == null) {
			return;
		}
		if (!DbConfig.get(rid).flows.contains(context.flowVolume().flow()) || !DbConfig.get(rid).debug) {
			DbConfig.remove(rid);
		}

	}

	@Override
	public void beginActionInvoke(IAction action, IFlowContext context, IFlowResultCtn result) {
		String rid = FlowInstruUtils.getXruleRid(context);
		if (DbConfig.get(rid) == null || !DbConfig.get(rid).debug) {
			return;
		}
		if (debugAnywarPermit(rid,"actionInvokePre") || actionPermit(rid,"actionInvokePre", action.action())
				|| operationPermit(rid,"actionInvokePre", action.operation())
				|| spelCondtionPermit(rid,"actionInvokePre", action)) {
			invokeDebug(rid,"actionInvokePre", action, context, result);
		}
	}

	@Override
	public void endActionInvoke(IAction action, IFlowContext context, IFlowResultCtn result, IFlowResult flowResult) {
		String rid = FlowInstruUtils.getXruleRid(context);
		if (DbConfig.get(rid) == null || !DbConfig.get(rid).debug) {
			return;
		}

		if (debugAnywarPermit(rid,"actionInvokePost") || actionPermit(rid,"actionInvokePost", action.action())
				|| operationPermit(rid,"actionInvokePost", action.operation())
				|| signalPermit(rid,"actionInvokePost", flowResult.signal())
				|| spelCondtionPermit(rid,"actionInvokePost", action)) {
			invokeDebug(rid,"actionInvokePost", flowResult.data(), flowResult.signal(), action, context, result,
					flowResult);
		}

	}

	@Override
	public void beginFilter(FlowFilter flowFilter, FlowInvocation invocation) {
		String rid = invocation.flowVolume().rid();
		if (DbConfig.get(rid) == null || !DbConfig.get(rid).debug) {
			return;
		}
		if (debugAnywarPermit(rid,"beginFilter") || actionPermit(rid,"beginFilter", invocation.action().action())
				|| operationPermit(rid,"beginFilter", invocation.action().operation())
				|| filterPermit(rid,"beginFilter", flowFilter.fileterName())
				|| spelCondtionPermit(rid,"beginFilter", invocation.action())) {
			invokeDebug(rid,"beginFilter", flowFilter, invocation);
		}

	}
	
	@Override
	public void endFilter(FlowFilter flowFilter, FlowInvocation invocation, FlowResult filterResult) {
		String rid = invocation.flowVolume().rid();
		if (DbConfig.get(rid) == null || !DbConfig.get(rid).debug) {
			return;
		}
		if (debugAnywarPermit(rid,"endFilter") || actionPermit(rid,"endFilter", invocation.action().action())
				|| operationPermit(rid,"endFilter", invocation.action().operation())
				|| filterPermit(rid,"endFilter", flowFilter.fileterName())
				|| signalPermit(rid,"endFilter", filterResult.actionResult().signal())
				|| spelCondtionPermit(rid,"endFilter", invocation.action())) {
			invokeDebug(rid,"endFilter", flowFilter, invocation,filterResult);
		}
		
	}

	@Override
	public void endFlow(IFlowResultCtn result) {
		String rid = result.flowContext().flowVolume().rid();
		if (DbConfig.get(rid) == null || !DbConfig.get(rid).debug) {
			return;
		}
		invokeDebug(rid,"endFlow", result);
		DbConfig.remove(rid);
	}

	@Override
	public void beginActionExecute(IAction action, IDataVolume dataVolume, IFlowVolume flowVolume) {
		String rid = flowVolume.rid();
		if (DbConfig.get(rid) == null || !DbConfig.get(rid).debug) {
			return;
		}
		if (debugAnywarPermit(rid,"actionExecutePre") || actionPermit(rid,"actionExecutePre", action.action())
				|| operationPermit(rid,"actionExecutePre", action.operation())
				|| spelCondtionPermit(rid,"actionExecutePre", action)) {
			invokeDebug(rid,"actionExecutePre", FlowSpelUtil.spelGetData(action, action.extKey()), action.content(), action,
					dataVolume, flowVolume);
		}

	}

	@Override
	public void endActionExecute(IAction action, IDataVolume dataVolume, IFlowVolume flowVolume,
			IActionResult actionResult) {
		String rid = flowVolume.rid();
		if (DbConfig.get(rid) == null || !DbConfig.get(rid).debug) {
			return;
		}
		if (debugAnywarPermit(rid,"actionExecutePost") || actionPermit(rid,"actionExecutePost", action.action())
				|| operationPermit(rid,"actionExecutePost", action.operation())
				|| signalPermit(rid,"actionExecutePost", actionResult.signal())
				|| spelCondtionPermit(rid,"actionExecutePost", action)) {
			invokeDebug(rid,"actionExecutePost", actionResult.data(), actionResult.signal(), action, dataVolume, flowVolume,
					actionResult);
		}

	}

	void invokeDebug(String rid,String methodName, Object... args) {
		Method method = getMethod(rid,methodName);
		try {
			method.invoke(DbConfig.get(rid).debugInstance, args);
		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
			e.printStackTrace();
		}
	}

	Method getMethod(String rid,String method) {
		if (DbConfig.get(rid) == null || !DbConfig.get(rid).debug) {
			return null;
		}

		Map<Method, DebugConfItem> interceptions = DbConfig.get(rid).interceptions;
		Optional<Map.Entry<Method, DebugConfItem>> opt = interceptions.entrySet().stream()
				.filter(m -> method.equals(m.getKey().getName())).findFirst();
		if (opt.isPresent()) {
			return opt.get().getKey();
		}
		return null;
	}

	DebugConfItem getDci(String rid,String method) {
		if (DbConfig.get(rid) == null || !DbConfig.get(rid).debug) {
			return null;
		}

		Map<Method, DebugConfItem> interceptions = DbConfig.get(rid).interceptions;
		Optional<Map.Entry<Method, DebugConfItem>> opt = interceptions.entrySet().stream()
				.filter(m -> method.equals(m.getKey().getName())).findFirst();
		if (opt.isPresent()) {
			return opt.get().getValue();
		}
		return null;
	}

	boolean debugAnywarPermit(String rid,String method) {
		return debugAnywarPermit(getDci(rid,method));
	}

	boolean debugAnywarPermit(DebugConfItem dci) {
		return dci.debugAnyway;
	}

	boolean actionPermit(String rid,String method, String action) {
		return actionPermit(getDci(rid,method), action);
	}

	boolean actionPermit(DebugConfItem dci, String action) {
		if (dci.actions.isEmpty()) {
			return false;
		}
		if (dci.actions.contains(action)) {
			return true;
		} else {
			return false;
		}
	}

	boolean filterPermit(String rid,String method, String filter) {
		return filterPermit(getDci(rid,method), filter);
	}

	boolean filterPermit(DebugConfItem dci, String filter) {
		if (dci.filters.isEmpty()) {
			return false;
		}
		if (dci.filters.contains(filter)) {
			return true;
		} else {
			return false;
		}
	}

	boolean operationPermit(String rid,String method, OperationType operationType) {
		return operationPermit(getDci(rid,method), operationType);
	}

	boolean operationPermit(DebugConfItem dci, OperationType operationType) {
		if (dci.operationTypes.isEmpty()) {
			return false;
		}
		if (dci.operationTypes.contains(operationType)) {
			return true;
		} else {
			return false;
		}
	}

	boolean signalPermit(String rid,String method, Signal signal) {
		return signalPermit(getDci(rid,method), signal);
	}

	boolean signalPermit(DebugConfItem dci, Signal signal) {
		if (dci.resultSignals.isEmpty()) {
			return false;
		}
		if (dci.resultSignals.contains(signal)) {
			return true;
		} else {
			return false;
		}
	}

	boolean spelCondtionPermit(String rid,String method, IAction action) {
		return spelCondtionPermit(getDci(rid,method), action);
	}

	boolean spelCondtionPermit(DebugConfItem dci, IAction action) {
		if (dci.spelConditions.isEmpty()) {
			return false;
		}
		for (String el : dci.spelConditions) {
			try {
				boolean temp = FlowSpelUtil.spelGetData(action, el, Boolean.class);
				if (temp) {
					return temp;
				}
			} catch (Exception e) {
			}
		}
		return false;
	}

	void anayliseDebugType(String rid) {
		try {
			Class<?> debugClazz = Class.forName(debugType);
			Object debugInstance = debugClazz.newInstance();
			Method[] methods = debugClazz.getDeclaredMethods();
			String[] methodNames = { "actionExecutePre", "actionExecutePost", "actionInvokePre", "actionInvokePost",
					"beginFilter","endFilter", "endFlow" };
			List<Method> debugMethod = Stream.of(methods).filter(m -> Arrays.asList(methodNames).contains(m.getName()))
					.collect(Collectors.toList());
			XdebugConf xdebugConf = debugClazz.getAnnotation(XdebugConf.class);
			if (xdebugConf == null) {
				return;
			}
			DebugConf debugConf = new DebugConf();
			debugConf.debug = xdebugConf.debug();
			debugConf.flows = Arrays.asList(xdebugConf.flow());
			debugConf.debugInstance = debugInstance;

			debugMethod.forEach(m -> {
				XdebugConditon xdebugProcess = m.getAnnotation(XdebugConditon.class);
				DebugConfItem debugConfItem = new DebugConfItem();
				debugConf.interceptions.put(m, debugConfItem);
				if (xdebugProcess != null) {
					debugConfItem.actions = Arrays.asList(xdebugProcess.action());
					debugConfItem.filters = Arrays.asList(xdebugProcess.filter());
					debugConfItem.operationTypes = Arrays.asList(xdebugProcess.operationType());
					debugConfItem.resultSignals = Arrays.asList(xdebugProcess.resultSignal());
					debugConfItem.spelConditions = Arrays.asList(xdebugProcess.spelCondition());
					debugConfItem.debugAnyway = xdebugProcess.debugAnyway();
				}
			});
			DbConfig.put(rid, debugConf);

		} catch (Exception e) {

		}
	}

	class DebugConf {

		private boolean debug = true;
		private List<String> flows = new ArrayList<String>();
		private Object debugInstance;
		private Map<Method, DebugConfItem> interceptions = new HashMap<Method, DebugInstrumentation.DebugConfItem>();

		public boolean isDebug() {
			return debug;
		}

		public void setDebug(boolean debug) {
			this.debug = debug;
		}

		public List<String> getFlows() {
			return flows;
		}

		public void setFlows(List<String> flows) {
			this.flows = flows;
		}

		public Map<Method, DebugConfItem> getInterceptions() {
			return interceptions;
		}

		public void setInterceptions(Map<Method, DebugConfItem> interceptions) {
			this.interceptions = interceptions;
		}

		public Object getDebugInstance() {
			return debugInstance;
		}

		public void setDebugInstance(Object debugInstance) {
			this.debugInstance = debugInstance;
		}

	}

	class DebugConfItem {

		private List<String> actions = new ArrayList<String>();
		private List<String> filters = new ArrayList<String>();
		private List<OperationType> operationTypes = new ArrayList<>();
		private List<Signal> resultSignals = new ArrayList<>();
		private List<String> spelConditions = new ArrayList<String>();
		private boolean debugAnyway = false;

		public List<String> getSpelConditions() {
			return spelConditions;
		}

		public void setSpelConditions(List<String> spelConditions) {
			this.spelConditions = spelConditions;
		}

		public boolean isDebugAnyway() {
			return debugAnyway;
		}

		public void setDebugAnyway(boolean debugAnyway) {
			this.debugAnyway = debugAnyway;
		}

		public List<String> getActions() {
			return actions;
		}

		public void setActions(List<String> actions) {
			this.actions = actions;
		}

		public List<String> getFilters() {
			return filters;
		}

		public void setFilters(List<String> filters) {
			this.filters = filters;
		}

		public List<OperationType> getOperationTypes() {
			return operationTypes;
		}

		public void setOperationTypes(List<OperationType> operationTypes) {
			this.operationTypes = operationTypes;
		}

		public List<Signal> getResultSignals() {
			return resultSignals;
		}

		public void setResultSignals(List<Signal> resultSignals) {
			this.resultSignals = resultSignals;
		}

	}

	@Override
	public String label() {
		return "debug";
	}

}
